﻿using System;
using System.Net;
using System.Xml;
using System.Collections.Generic;
using System.Reflection;
using System.Linq.Expressions;
using UIT.Markup;
using System.ComponentModel;
using System.Xml.Serialization;

namespace UIT.Sl.Xaml
{
	public class CacheAccessor
	{
		public CacheAccessor(string name, Delegate accessor, bool isContent)
		{
			Name = name;
			Accessor = accessor;
			IsContentProperty = isContent;
		}

		public string Name { get; private set; }

		public Delegate Accessor { get; private set; }

		public object Invoke(object target)
		{
			return Accessor.DynamicInvoke(target);
		}

		public bool IsContentProperty { get; private set; }
	}

	public class XamlCacheWriter
	{
		static Dictionary<Type, IEnumerable<CacheAccessor>> accessors = new Dictionary<Type, IEnumerable<CacheAccessor>>();

		public static IEnumerable<CacheAccessor> GetCachedAccessors(Type type)
		{
			IEnumerable<CacheAccessor> result;
			lock (XamlCacheWriter.accessors)
			{
				if (XamlCacheWriter.accessors.TryGetValue(type, out result))
					return result;

				IList<CacheAccessor> accessors = new List<CacheAccessor>();
				var parameter = Expression.Parameter(type);
				bool isBindingContainer = typeof(IBindingContainer).IsAssignableFrom(type);
				var binding = Expression.Variable(typeof(Binding), "binding");
				var attributes = type.GetCustomAttributes(typeof(System.Windows.Markup.ContentPropertyAttribute), true);
				System.Windows.Markup.ContentPropertyAttribute contentProperty = null;
				if (attributes != null && attributes.Length > 0)
					contentProperty = (System.Windows.Markup.ContentPropertyAttribute)attributes[0];

				foreach (PropertyInfo pi in type.GetProperties())
				{
					if (!pi.CanWrite || pi.GetCustomAttributes(typeof(XmlIgnoreAttribute), true).Length > 0)
						continue;
					if (isBindingContainer)
					{
						LabelTarget @return = Expression.Label(typeof(object));
						accessors.Add(new CacheAccessor(pi.Name, Expression.Lambda(typeof(Func<,>).MakeGenericType(type, typeof(object)), Expression.Block(
							new ParameterExpression[] { binding },
							Expression.Assign(binding, Expression.Call(parameter, "GetBinding", null, Expression.Constant(pi.Name))),
							Expression.IfThenElse(Expression.Equal(binding, Expression.Constant(null)),
								Expression.Return(@return, Expression.Convert(Expression.Property(parameter, pi), typeof(object))),
								Expression.Return(@return, Expression.Call(binding, "ToString", null))),
								Expression.Label(@return, Expression.Constant(null))),
							parameter).Compile(), contentProperty != null && pi.Name == contentProperty.Name));
					}
					else
					{
						accessors.Add(new CacheAccessor(pi.Name, Expression.Lambda(Expression.Property(parameter, pi), parameter).Compile(), contentProperty != null && pi.Name == contentProperty.Name));
					}
				}

				XamlCacheWriter.accessors.Add(type, accessors);
				return accessors;
			}
		}

		public static HashSet<string> FindNeededNamespaces(object obj, HashSet<string> result = null)
		{
			if (obj == null)
				return null;

			if (WriteAsAttribute(obj))
				return null;

			if (result == null)
				result = new HashSet<string>();

			System.Collections.IList list = obj as System.Collections.IList;
			if (list != null)
			{
				return FindNeededNamespaces(list, result);
			}
			result.Add(obj.GetType().Namespace);
			foreach (CacheAccessor accessor in GetCachedAccessors(obj.GetType()))
			{
				FindNeededNamespaces(accessor.Invoke(obj), result);
			}
			return result;
		}

		public static HashSet<string> FindNeededNamespaces(System.Collections.IList list, HashSet<string> result = null)
		{
			if (list == null)
				return null;
			if (list.Count == 0)
				return null;
			foreach (object obj in list)
				result = FindNeededNamespaces(obj, result);

			return result;
		}

		public static bool WriteAsAttribute(object result)
		{
			TypeConverter converter = new TypeConverter();
			Type resultType = result.GetType();
			object[] attributes = resultType.GetCustomAttributes(typeof(TypeConverterAttribute), true);
			if (attributes != null && attributes.Length > 0)
				converter = (TypeConverter)Activator.CreateInstance(Type.GetType(((TypeConverterAttribute)attributes[0]).ConverterTypeName));
			return result.GetType().IsPrimitive || result.GetType().IsEnum || result is string || converter.CanConvertTo(typeof(string));
		}
	}
}
